Debugging Python Programs Using Breakpoints and Debuggers
Course: Python Training
Topic: Debugging Python Programs Using Breakpoints and Debuggers
Course Link: Python Training Course at JustAcademy
Demo Link: Register for a Course Demo
Website: JustAcademy Official Website
1. Introduction to Debugging in Python
Debugging is the process of identifying, analyzing, and correcting errors in a computer program. During Python development, a program may crash, produce incorrect results, or behave differently from what the developer expects. Debugging helps developers locate the root cause of these problems and implement reliable solutions.
Python supports several debugging techniques, including print statements, breakpoints, the built-in pdb debugger, and graphical debugging tools in integrated development environments (IDEs). Breakpoints are especially useful because they allow developers to pause execution at a specific line and inspect variables, expressions, function calls, and program state.
Learning Objectives
- Understand debugging and its importance in Python development.
- Identify syntax, runtime, and logical errors.
- Understand how breakpoints work.
- Use Python's built-in
breakpoint() function.
- Debug programs using the
pdb module.
- Inspect variables, expressions, and function arguments.
- Use step into, step over, step out, and continue operations.
- Apply conditional breakpoints and inspect the call stack.
- Debug loops, functions, classes, and exceptions.
- Follow systematic debugging practices in real-world applications.
2. Understanding Bugs and Programming Errors
A bug is a defect in a program that causes incorrect output, unexpected behavior, or application failure. Identifying the category of an error helps determine which debugging technique is most appropriate.
| Error Type |
Description |
Example |
| Syntax Error |
The code violates Python's syntax rules. |
if age >= 18 print("Adult") |
| Runtime Error |
An error occurs while the program executes. |
10 / 0 |
| Logical Error |
The program runs but produces an incorrect result. |
Using addition instead of multiplication in a calculation. |
| Type Error |
An operation is performed on incompatible data types. |
"10" + 5 |
| Name Error |
A name is referenced when it has not been defined in the applicable scope. |
print(total_amount) when the name is undefined. |
| Index Error |
A sequence is accessed using an invalid index. |
items[10] when the list contains only three elements. |
| Data Error |
Unexpected or invalid input leads to incorrect behavior. |
Calculating an average from an empty list. |
Syntax errors are often detected before a program runs. Debuggers are particularly useful for investigating runtime errors and logical errors because they allow developers to inspect the program's internal state.
3. What Is a Debugger?
A debugger is a development tool that allows programmers to control program execution and examine the internal state of an application. Instead of running the entire program and checking only its final output, a developer can pause execution, inspect values, execute statements individually, and follow function calls.
Main Features of a Debugger
- Pause execution: Stop the program at a selected location.
- Inspect variables: Examine current values and data types.
- Evaluate expressions: Calculate expressions using the current program state.
- Step through code: Execute statements individually.
- Inspect function calls: Understand how execution reaches a particular line.
- Set breakpoints: Specify where execution should pause.
- Investigate exceptions: Examine the state associated with a failure.
- Resume execution: Continue running the program after inspection.
Popular Python Debugging Techniques and Tools
| Tool or Technique |
Description |
pdb |
Python's built-in interactive source-code debugger. |
breakpoint() |
A built-in function that invokes the configured debugging hook. |
| Visual Studio Code Debugger |
Provides graphical breakpoints, variable inspection, and stepping controls. |
| PyCharm Debugger |
Provides an integrated debugging interface for Python projects. |
| Print Debugging |
Uses temporary print statements to display intermediate values. |
4. Understanding Breakpoints
A breakpoint is a marker placed at a specific location in source code where a debugger can pause program execution. Once execution pauses, developers can inspect variables, evaluate expressions, and follow the next statements.
For example, if a shopping cart calculates an incorrect total, a breakpoint inside the calculation loop allows you to inspect each item's price, quantity, and contribution to the total.
Why Are Breakpoints Important?
- They help identify the exact location where incorrect behavior occurs.
- They allow developers to inspect values without adding many print statements.
- They make it easier to understand loops and conditional branches.
- They reveal unexpected function arguments and return values.
- They support efficient investigation of complex applications.
Example: Finding an Incorrect Total
prices = [100, 200, 300]
total = 0
for price in prices:
total = price
print("Total:", total)
The expected total is 600, but the program prints 300 because the previous value of total is overwritten during every iteration.
Place a breakpoint inside the loop and inspect price and total. You will discover that the program does not accumulate the values.
The corrected statement is:
total += price
The corrected program produces the expected total of 600.
5. Using the Built-in breakpoint() Function
Python 3.7 and later provide the built-in breakpoint() function. It invokes the configured debugging hook. By default, this starts Python's built-in debugger, pdb, and pauses execution at the location where the function is called.
Basic Example
def calculate_total(price, quantity):
breakpoint()
total = price * quantity
return total
result = calculate_total(50, 3)
print("Total:", result)
When Python reaches breakpoint(), execution pauses before the multiplication. You can inspect the function parameters and evaluate the calculation manually.
Example Debugging Commands
(Pdb) p price
50
(Pdb) p quantity
3
(Pdb) p price * quantity
150
(Pdb) n
(Pdb) p total
150
(Pdb) c
The p command evaluates and prints an expression. The n command advances execution to the next line without stepping into a called function, while c resumes execution until the next breakpoint or program termination.
Important Considerations
- Use
breakpoint() when you need to inspect a particular location.
- Remove temporary breakpoint calls when debugging is complete.
- Avoid unintended interactive pauses in automated jobs or production services.
- The
PYTHONBREAKPOINT environment variable can change the behavior of the built-in function.
6. Debugging with Python's pdb Module
The pdb module is included with Python and provides an interactive debugging environment. It allows developers to inspect source code, set breakpoints, execute statements step by step, and examine the call stack without installing an additional package.
Starting a Python Program with pdb
Save the following code as debug_example.py:
def divide_numbers(a, b):
result = a / b
return result
first = 20
second = 4
breakpoint()
answer = divide_numbers(first, second)
print("Answer:", answer)
Open a terminal in the directory containing the file and run:
python debug_example.py
When the program reaches breakpoint(), the debugger displays an interactive prompt. You can inspect variables, evaluate expressions, and control execution.
You can also start the script under debugger control from the beginning:
python -m pdb debug_example.py
Common pdb Commands
| Command |
Purpose |
help |
Displays available debugger commands. |
n / next |
Executes the next line without entering a called function. |
s / step |
Executes the next line and enters a function call when applicable. |
c / continue |
Resumes execution until another breakpoint or program completion. |
l / list |
Displays source code around the current location. |
p expression |
Evaluates and prints an expression. |
pp expression |
Pretty-prints a value for easier reading. |
ll |
Displays the full source code of the current function or frame when available. |
w / where |
Displays the current stack trace. |
u / up |
Moves to the calling stack frame. |
d / down |
Moves to a deeper stack frame. |
r / return |
Continues execution until the current function returns. |
q / quit |
Exits the debugger. |
Example Debugging Session
(Pdb) p first
20
(Pdb) p second
4
(Pdb) n
(Pdb) s
(Pdb) p a
20
(Pdb) p b
4
(Pdb) n
(Pdb) p result
5.0
(Pdb) c
The exact sequence depends on the current execution location and source file. Use help or help command_name to learn more about a particular debugger command.
7. Setting, Managing, and Removing Breakpoints
In pdb, breakpoints can be set interactively instead of inserting a breakpoint() call in the source code. This helps developers investigate problems without changing the program.
| Command |
Purpose |
Example |
b / break |
Sets a breakpoint at a line or function. |
b 15 |
tbreak |
Sets a temporary breakpoint that is removed after being hit. |
tbreak 20 |
cl / clear |
Clears a breakpoint. |
cl 1 |
disable |
Temporarily disables a breakpoint. |
disable 1 |
enable |
Re-enables a disabled breakpoint. |
enable 1 |
condition |
Sets a condition for a breakpoint. |
condition 1 count == 5 |
ignore |
Ignores a breakpoint for a specified number of hits. |
ignore 1 3 |
info break |
Lists breakpoint details and status. |
info break |
Breakpoint numbers are assigned by the debugger. Check the output of break or info break before referring to a particular breakpoint number.
8. Conditional Breakpoints
A conditional breakpoint pauses execution only when a specified expression evaluates to true. It is especially useful when debugging long loops or programs where a problem occurs only for particular input values.
Example
scores = [45, 78, 32, 90, 28, 84]
for index, score in enumerate(scores):
if score < 40:
print("Low score:", score)
Suppose you want the debugger to pause only when a score is below 40. Set a breakpoint on the relevant executable line in your IDE and enter score < 40 as the condition.
In pdb, if breakpoint 1 is already set on the appropriate line, use:
condition 1 score < 40
The debugger will pause when the breakpoint is reached and the condition is satisfied. The variable used in the condition must be available at that execution point.
Advantages of Conditional Breakpoints
- Reduce unnecessary interruptions during loops.
- Help find unusual values in large datasets.
- Allow investigation of specific transaction amounts or identifiers.
- Make it easier to reproduce rare execution states.
9. Step Into, Step Over, and Step Out
Stepping controls allow you to follow program execution one statement at a time. They are useful when a bug involves multiple function calls or complicated logic.
| Operation |
Behavior |
When to Use It |
| Step Into |
Moves to the next execution point and enters a called function when applicable. |
When you suspect a problem inside a function. |
| Step Over |
Executes the current line without entering a called function. |
When the called function is not the focus of the investigation. |
| Step Out |
Continues until the current function returns to its caller. |
When you have finished inspecting a function. |
| Continue |
Resumes execution until another breakpoint or program termination. |
When you want the program to proceed normally. |
Example with Multiple Functions
def calculate_discount(price, percentage):
discount = price * percentage / 100
return discount
def final_price(price, percentage):
discount = calculate_discount(price, percentage)
return price - discount
result = final_price(1000, 10)
print(result)
Place a breakpoint inside final_price(). Step into calculate_discount() to inspect the calculation. Verify price, percentage, and discount. Then step out to return to the calling function and inspect the final result.
10. Inspecting Variables and Expressions
When a program pauses, inspect the values that influence its behavior. A variable may contain a different value from what you expect because of an incorrect assignment, an unexpected function call, or a change to a mutable object.
Example
quantity = 4
unit_price = 125
total = quantity + unit_price
breakpoint()
print("Total:", total)
At the debugger prompt, enter:
(Pdb) p quantity
4
(Pdb) p unit_price
125
(Pdb) p total
129
(Pdb) p quantity * unit_price
500
The program returns 129 because the code uses addition. If the intended calculation is a purchase total, multiplication is required.
What Should You Inspect?
- Function arguments and return values.
- Loop counters and current loop elements.
- Values before and after calculations.
- Boolean expressions controlling conditional branches.
- Collection lengths and contents.
- Object attributes and data types.
- Values received from external functions or APIs.
You can use type(variable) to inspect a value's type. Be cautious when evaluating expressions that call functions because they may modify state or trigger external operations.
11. Understanding the Call Stack
The call stack records the sequence of active function calls that led to the current execution point. When one function calls another, Python creates a new stack frame for the called function. The stack provides context about how the program reached a particular line.
Example
def get_price():
price = 250
return price
def calculate_tax():
price = get_price()
tax = price * 0.18
return tax
def main():
tax_amount = calculate_tax()
print("Tax:", tax_amount)
main()
If execution pauses inside calculate_tax(), the call stack helps show that it was called by main(). Use where or w in pdb to inspect the stack, and use up or down to move between frames.
Why the Call Stack Is Useful
- It helps identify the origin of unexpected arguments.
- It reveals the sequence of function calls leading to an error.
- It helps debug deeply nested function calls.
- It provides context when exceptions occur across multiple functions.
12. Debugging Python Programs in an IDE
Integrated development environments such as Visual Studio Code and PyCharm provide graphical debugging tools. Although their interfaces differ, the general workflow is similar.
Typical Debugging Workflow
- Open the Python file or project.
- Set a breakpoint by clicking beside the relevant line number.
- Select the correct Python interpreter or virtual environment.
- Start the program using the debugger.
- Reproduce the behavior you want to investigate.
- Inspect variables in the debugger's variables panel.
- Use stepping controls to follow the relevant code.
- Modify the program after identifying the root cause.
- Run the program again to verify the correction.
Common IDE Debugging Features
| Feature |
Description |
| Breakpoints Panel |
Displays breakpoints and allows you to manage their status. |
| Variables Panel |
Shows variables and their current values in the active context. |
| Watch Expressions |
Tracks selected expressions during debugging. |
| Call Stack Panel |
Shows active function calls and supports navigation between frames. |
| Debug Console |
Allows expressions to be evaluated in the debugging context. |
| Exception Breakpoints |
Can pause execution when selected exceptions are raised. |
Some IDEs also support conditional breakpoints and logpoints. Consult your editor's documentation for exact controls and configuration options.
13. Debugging Logical Errors Step by Step
Logical errors can be difficult to find because the program may execute without raising an exception. A reliable approach is to reproduce the problem with a small input, pause near the suspected code, and inspect intermediate results.
Example: Incorrect Average Calculation
def calculate_average(numbers):
total = 0
for number in numbers:
total = number
return total / len(numbers)
values = [10, 20, 30, 40]
print(calculate_average(values))
The expected average is 25, but the program returns 10 because the total is overwritten during each iteration.
Debugging Steps
- Run the program and confirm the unexpected result.
- Place a breakpoint inside the loop.
- Inspect
number and total on each iteration.
- Identify the assignment that overwrites the previous total.
- Replace the assignment with an accumulation operation.
- Test the corrected program using multiple inputs.
Corrected Code
def calculate_average(numbers):
if not numbers:
raise ValueError("numbers must not be empty")
total = 0
for number in numbers:
total += number
return total / len(numbers)
values = [10, 20, 30, 40]
print(calculate_average(values)) # 25.0
This version accumulates the numbers correctly and explicitly handles an empty list.
14. Debugging Exceptions and Runtime Errors
When Python raises an exception, the traceback identifies the exception type and the sequence of function calls that led to it. A debugger can help inspect the values that caused the failure.
Example: Division by Zero
def safe_divide(a, b):
breakpoint()
return a / b
print(safe_divide(10, 0))
When execution pauses, inspect a and b. The denominator is zero, so executing the division raises ZeroDivisionError.
Correcting the Problem
def safe_divide(a, b):
if b == 0:
raise ValueError("The denominator must not be zero")
return a / b
The correct error-handling strategy depends on the application. Do not simply suppress every exception; identify the reason for the failure and respond appropriately.
Post-Mortem Debugging
Post-mortem debugging allows you to inspect program state after an unhandled exception. Start the program under the debugger using:
python -m pdb your_script.py
When an unhandled exception occurs, pdb can enter post-mortem debugging so that you can inspect the traceback and stack frames. This can be useful when you do not know in advance where to insert a breakpoint.
15. Debugging Loops and Conditional Statements
Loops and conditional statements can contain off-by-one errors, incorrect comparisons, and unexpected changes to variables. Breakpoints make it possible to observe how the program state changes during execution.
Example: Off-by-One Error
for number in range(1, 5):
print(number)
The program prints 1, 2, 3, and 4 because the stop value in range() is excluded. If you need to print through 5, use range(1, 6).
Loop Debugging Checklist
- Check the initial value of the loop variable.
- Verify the start and stop values of
range().
- Inspect the condition that controls the loop.
- Confirm that variables change as expected.
- Look for accidental modification of collections.
- Use conditional breakpoints when only particular iterations matter.
- Confirm that the loop terminates correctly.
16. Debugging Functions, Classes, and Objects
Object-oriented programs introduce additional debugging concerns, including unexpected method arguments, incorrect return values, modified instance attributes, and overridden methods that behave differently from the base implementation.
Example: Inspecting Object State
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
breakpoint()
self.balance -= amount
account = BankAccount(500)
account.withdraw(100)
print(account.balance)
At the breakpoint, inspect self.balance and amount. Confirm that the account has the expected starting balance and that the withdrawal amount is valid. A real banking application would also require validation for negative withdrawals and overdrafts.
Important Checks
- Confirm that the correct object instance is being used.
- Inspect instance attributes before and after method calls.
- Verify method arguments and return values.
- Look for unexpected changes to mutable objects.
- Check whether a subclass overrides a method.
- Confirm that returned values have the expected type.
17. Debugging Best Practices
- Reproduce the problem: Create a repeatable example that demonstrates the bug.
- Reduce complexity: Test with a small input before debugging a large dataset.
- Use targeted breakpoints: Pause near the suspected source of the problem.
- Verify assumptions: Inspect actual values instead of relying on expectations.
- Follow execution flow: Step through the relevant statements in order.
- Change one thing at a time: Make it easier to identify which correction resolves the problem.
- Retest the fix: Test the original case and relevant edge cases.
- Use automated tests: Add regression tests to prevent the bug from returning.
- Remove temporary debugging code: Clean up unnecessary breakpoint calls before deployment.
- Protect sensitive information: Avoid exposing passwords, tokens, or private production data in debugger output.
- Preserve useful error handling: Do not hide unexpected exceptions without a clear reason.
18. Common Debugging Problems and Solutions
| Problem |
Possible Cause |
Solution |
| Breakpoint is not reached. |
The line is not executed, or the wrong run configuration is active. |
Verify the execution path, active file, and debugger configuration. |
| Debugger pauses too often. |
Many breakpoints are active or a breakpoint is inside a frequently executed loop. |
Disable unnecessary breakpoints or add a condition. |
| Variable is undefined. |
The variable is outside the current scope or has not yet been assigned. |
Inspect the current stack frame and execution point. |
| Values change unexpectedly. |
A function or another part of the program modifies shared state. |
Set breakpoints before and after the relevant operation. |
| Program appears to hang. |
The debugger is waiting for a command or the program is blocked on input. |
Check the debugger prompt and the program's current execution state. |
| Fix does not change output. |
The wrong file is running or another defect remains. |
Confirm the executed file and repeat the debugging process. |
| Stack trace is confusing. |
Nested function calls make the source of the problem unclear. |
Use where, up, and down to inspect stack frames. |
| Interactive debugging does not work in automation. |
The environment lacks an interactive terminal or captures input and output. |
Use automated tests, structured logs, or a non-interactive debugging workflow. |
19. Practical Exercise: Debug a Shopping Cart
Use breakpoints to find and correct the error in the following shopping-cart program.
def calculate_cart_total(items):
total = 0
for item in items:
price = item["price"]
quantity = item["quantity"]
total = price * quantity
return total
cart = [
{"price": 100, "quantity": 2},
{"price": 50, "quantity": 3},
{"price": 25, "quantity": 4}
]
print("Cart total:", calculate_cart_total(cart))
Tasks
- Run the program and record its output.
- Set a breakpoint inside the loop.
- Inspect
price, quantity, and total during each iteration.
- Explain why the final result does not represent the sum of all items.
- Correct the accumulation logic.
- Test the program with an empty cart and a cart containing one item.
- Consider how to handle missing dictionary keys or invalid prices.
Expected Correction
def calculate_cart_total(items):
total = 0
for item in items:
price = item["price"]
quantity = item["quantity"]
total += price * quantity
return total
For the supplied cart, the correct total is 450. This exercise demonstrates how inspecting intermediate values can reveal a logical error that does not necessarily cause an exception.
20. Mini Project: Debugging a Student Grade Calculator
Build a small application that calculates a student's average and assigns a grade. Use breakpoints to investigate unexpected results and correct the logical errors.
Starter Code
def calculate_average(marks):
total = 0
for mark in marks:
total = mark
average = total / len(marks)
return average
def get_grade(average):
if average > 90:
return "A"
elif average > 75:
return "B"
elif average > 50:
return "C"
else:
return "Needs Improvement"
marks = [85, 92, 78, 88]
average = calculate_average(marks)
grade = get_grade(average)
print("Average:", average)
print("Grade:", grade)
Project Requirements
- Place a breakpoint inside
calculate_average().
- Inspect the total after each loop iteration.
- Correct the total calculation.
- Check the boundary conditions in
get_grade().
- Handle an empty list of marks.
- Test values exactly at each grade threshold.
- Run the program again to verify the corrected results.
Possible Improved Implementation
def calculate_average(marks):
if not marks:
raise ValueError("Marks cannot be empty")
total = sum(marks)
return total / len(marks)
def get_grade(average):
if average >= 90:
return "A"
elif average >= 75:
return "B"
elif average >= 50:
return "C"
else:
return "Needs Improvement"
marks = [85, 92, 78, 88]
average = calculate_average(marks)
grade = get_grade(average)
print("Average:", average)
print("Grade:", grade)
This implementation uses sum(), validates an empty list, and uses inclusive grade thresholds. These thresholds are examples and should be adjusted to match the application's grading rules.
21. Python Debugging Interview Questions and Answers
Q1. What is debugging in Python?
Debugging is the process of identifying, analyzing, and correcting defects in Python programs. It involves examining variables, execution flow, function calls, and errors.
Q2. What is a breakpoint?
A breakpoint is a location in source code where a debugger can pause program execution so that the developer can inspect the program's current state.
Q3. What does the breakpoint() function do?
It invokes the configured debugging hook. By default, it starts Python's built-in debugger, pdb, and allows interactive inspection of the program.
Q4. What is pdb?
pdb is Python's built-in interactive debugger. It supports stepping through code, setting breakpoints, inspecting expressions, and examining stack frames.
Q5. What is the difference between step and next?
step enters a function call when applicable, while next executes the current line and stops at the next line in the current frame, stepping over called functions.
Q6. What is a conditional breakpoint?
A conditional breakpoint pauses execution only when a specified expression evaluates to true. It is useful for investigating specific loop iterations or unusual values.
Q7. How do you inspect a variable in pdb?
Use the p command followed by the variable or expression, such as p total or p price * quantity.
Q8. What is a call stack?
The call stack represents the active chain of function calls that led to the current execution point. It helps developers understand how a particular line was reached.
Q9. How do you debug a logical error?
Reproduce the issue, set a breakpoint near the suspected code, inspect intermediate values, follow the execution flow, correct the logic, and test the result again.
Q10. Why should temporary breakpoints be removed before deployment?
Interactive breakpoints can pause normal application execution. Temporary debugging calls should be removed or deliberately disabled before production use.
Q11. Can a debugger help investigate exceptions?
Yes. A debugger can pause at or near an exception, expose active stack frames, and help inspect the variables involved in the failure.
Q12. What is the difference between print debugging and debugger-based debugging?
Print debugging displays selected values in program output. A debugger provides interactive control, stepping, variable inspection, breakpoints, and stack navigation.
22. Quick Revision Checklist
- Understand syntax errors, runtime errors, and logical errors.
- Know when to use breakpoints.
- Use
breakpoint() to enter the configured debugger.
- Start a script under
pdb using python -m pdb filename.py.
- Use
p to inspect variables and expressions.
- Use
n, s, r, and c to control execution.
- Use
where to inspect the call stack.
- Use conditional breakpoints for specific execution states.
- Inspect loop variables, function arguments, and return values.
- Correct the root cause rather than hiding the symptom.
- Retest the correction and remove unnecessary debugging calls.
23. Conclusion
Debugging Python programs using breakpoints and debuggers is an essential skill for developing reliable and maintainable applications. Breakpoints allow developers to pause execution at important locations, while tools such as pdb and IDE debuggers make it possible to inspect variables, follow function calls, investigate exceptions, and understand program behavior step by step.
By combining systematic debugging with clear tests and careful inspection of intermediate values, developers can identify root causes more efficiently, correct logical errors, and prevent recurring defects.
Continue learning with the Python Training Course at JustAcademy, register for a course demo, or visit the JustAcademy website.